package com.bjsxt.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.RandomUtil;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CreateCache;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bjsxt.domain.CashRecharge;
import com.bjsxt.domain.CashRechargeAuditRecord;
import com.bjsxt.domain.Coin;
import com.bjsxt.domain.Config;
import com.bjsxt.dto.AdminBankDto;
import com.bjsxt.dto.BuyGcnParamDto;
import com.bjsxt.dto.UserDto;
import com.bjsxt.feign.AdminBankFeignClient;
import com.bjsxt.feign.UserFeignClient;
import com.bjsxt.mapper.CashRechargeMapper;
import com.bjsxt.modle.CashRechargeVo;
import com.bjsxt.modle.CashTradeVo;
import com.bjsxt.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class CashRechargeServiceImpl extends ServiceImpl<CashRechargeMapper, CashRecharge> implements CashRechargeService {
    /**
     * 注入会员用户操作的 openFeign 对象
     */
    @Autowired
    private UserFeignClient userFeignClient;
    /**
     * 充值审核记录业务层对象
     */
    @Autowired
    private CashRechargeAuditRecordService cashRechargeAuditRecordService;
    /**
     * 用户财产记录业务层
     */
    @Autowired
    private AccountService accountService;
    /**
     * 平台参数配置业务层
     */
    @Autowired
    private ConfigService configService;
    /**
     * 币种业务层
     */
    @Autowired
    private CoinService coinService;
    /**
     * 注入 Openfeign 远程调用 adminBank 的对象
     */
    @Autowired
    private AdminBankFeignClient adminBankFeignClient;
    /**
     * 雪花算法类
     */
    @Autowired
    private Snowflake snowflake;
    /**
     * JetCache 分布式锁对象
     * 缓存的前缀 CASH_RECHARGE_LOCK
     * 缓存存储时间 60s
     * 在缓存和本地都存放 cacheType = CacheType.BOTH
     */
    @CreateCache(name = "CASH_RECHARGE_LOCK:", expire = 60)
    private Cache<String, String> cache;

    /**
     * 根据条件分页查询 GCN 充值记录
     *
     * @param page      分页对象<CashRecharge>
     * @param coinId    币种 ID
     * @param userId    用户 ID
     * @param userName  用户名
     * @param mobile    手机号码
     * @param status    状态
     * @param numMin    最小充值金额
     * @param numMax    最大充值金额
     * @param startTime 开始日期
     * @param endTime   结束日期
     * @return 查询出的分页数据对象
     */
    @Override
    public Page<CashRechargeVo> pageByCoinIdAndUserIdAndUserNameAndMobileAndStatusAndNumAndCreateTime(Page<CashRecharge> page, Long coinId, Long userId, String userName, String mobile, Byte status, Integer numMin, Integer numMax, Date startTime, Date endTime) {
        // 查询出的用户数据，用于第一次进行条件的限定查询，三个值都没有则正常的走分页查询，再构建数据，否则则进行先查询 id 再进行数据的拼接，两个条件的限定查询只会走一次
        Map<Long, UserDto> userDtoMap = null;
        // 用户的 id 集合
        Set<Long> ids = null;
        // 判断，三个值有一个有值的情况才进行查询 id
        if (userId != null || !StringUtils.isEmpty(userName) || !StringUtils.isEmpty(mobile)) {
            // 将三个条件传递到会员系统进行查询，返回 map key 就是 id
            userDtoMap = userFeignClient.getBasicUsersMap(userId == null ? null : Collections.singletonList(userId), userName, mobile);
            // 用户 id 集合
            ids = userDtoMap.keySet();
        }
        // 构建查询多条件查询
        QueryWrapper<CashRecharge> queryWrapper = new QueryWrapper<>();
        // 构建查询条件
        queryWrapper.in(ids != null && !ids.isEmpty(), CashRecharge.COL_USER_ID, ids);
        queryWrapper.eq(coinId != null, CashRecharge.COL_COIN_ID, coinId);
        queryWrapper.eq(status != null, CashRecharge.COL_STATUS, status);
        queryWrapper.between(numMin != null && numMax != null, CashRecharge.COL_NUM, numMin, numMax);
        queryWrapper.between(startTime != null && endTime != null, CashRecharge.COL_CREATED, startTime, endTime);
        // 调用方法进行数据的查询
        Page<CashRecharge> cashRechargePage = page(page, queryWrapper);
        // 如果在这里进行获取之后是空的没有获取到，那么就需要远程调用方法进行值的获取
        if (userDtoMap == null) {
            // 获取需要查询的 ids 集合
            List<Long> idList = cashRechargePage.getRecords().stream().map(CashRecharge::getUserId).collect(Collectors.toList());
            // 如果擦查询的 idList 是空则直接进行返回
            if (idList.isEmpty()) return new Page<>();
            // 根据 id 集合进行数据的查询
            userDtoMap = userFeignClient.getBasicUsersMap(idList, null, null);
        }
        // 如果擦查询的 userDtoMap 是空则直接进行返回
        if (userDtoMap.isEmpty()) return new Page<>();
        // 因为使用 lambda 表达式，下面这个 map 需要为 final 的
        Map<Long, UserDto> finalUserDtoMap = userDtoMap;
        // 拼接返回的 vo 对象，循环遍历返回的对象
        IPage<CashRechargeVo> cashRechargeVoPage = cashRechargePage.convert(cashRecharge -> {
            // 创建转换的 vo 对象
            CashRechargeVo cashRechargeVo = new CashRechargeVo();
            // 进行值的 copy
            BeanUtil.copyProperties(cashRecharge, cashRechargeVo);
            // 获取用户对象
            UserDto userDto = finalUserDtoMap.get(cashRechargeVo.getUserId());
            // 查询的数据不为空
            if (userDto != null) {
                // 设置昵称
                cashRechargeVo.setUserName(userDto.getUsername());
                // 设置真实姓名
                cashRechargeVo.setRealName(userDto.getRealName());
            }
            // 返回 vo 对象
            return cashRechargeVo;
        });
        // 返回 page 对象
        return (Page<CashRechargeVo>) cashRechargeVoPage;
    }

    /**
     * 根据用户 id 和审核状态查询 GCN 充值记录
     *
     * @param page   分页对象<CashRecharge>
     * @param userId 用户 id
     * @param status 状态：0-待审核；1-审核通过；2-拒绝；3-充值成功；
     * @return 查询出的分页数据对象
     */
    @Override
    public Page<CashRecharge> pageByUserIdAndStatus(Page<CashRecharge> page, Long userId, Byte status) {
        // 调用查询方法并返回
        return page(page, new LambdaQueryWrapper<CashRecharge>().eq(CashRecharge::getUserId, userId).eq(status != null, CashRecharge::getStatus, status));
    }

    /**
     * GCN 充值
     *
     * @param userId         需要进行重置的用户 id
     * @param buyGcnParamDto 购买 GCN 传输类对象
     * @return 现金交易数据响应对象
     */
    @Override
    public CashTradeVo buyGcn(Long userId, BuyGcnParamDto buyGcnParamDto) {
        // 校验现金参数合法性
        // 获取平台中现金充值的最小值
        long minNum = Long.parseLong(configService.getByCode("WITH_DROW").getValue());
        // 判断如果充值的数量小于平台充值的最小值，直接报错
        if (minNum > buyGcnParamDto.getNum().longValue())
            throw new IllegalArgumentException("充值数量不合法，最小充值数量为：" + minNum);
        // 查询我们公司的银行卡
        List<AdminBankDto> adminBanks = adminBankFeignClient.getAdminBanks();
        // 使用算法对银行卡进行平均分配获取一张银行卡
        AdminBankDto adminBankDto = balanced(adminBanks);
        // 生成订单号，参考号
        String orderId = snowflake.nextIdStr();
        String remark = RandomUtil.randomNumbers(6);
        // 获取币种信息
        Coin coin = coinService.getById(buyGcnParamDto.getCoinId());
        if (coin == null) throw new IllegalArgumentException("货币不存在");
        // 这里需要自己算到账金额，以免前端自己算的出现错误
        Config buyGcnRate = configService.getByCode("CNY2USDT");
        // 计算成交量（到账金额）
        BigDecimal mun = buyGcnParamDto.getNum().multiply(new BigDecimal(buyGcnRate.getValue())).setScale(2, RoundingMode.HALF_UP);
        // 在数据库中插入一条充值的记录
        CashRecharge cashRecharge = new CashRecharge();
        cashRecharge.setUserId(userId);
        cashRecharge.setCoinId(buyGcnParamDto.getCoinId());
        cashRecharge.setCoinName(coin.getName());
        cashRecharge.setNum(buyGcnParamDto.getNum());
        cashRecharge.setFee(BigDecimal.ZERO);
        cashRecharge.setMum(mun);
        cashRecharge.setType("linepay");
        cashRecharge.setTradeno(orderId);
        cashRecharge.setRemark(remark);
        cashRecharge.setStep((byte) 0);
        cashRecharge.setStatus((byte) 0);
        cashRecharge.setName(adminBankDto.getName());
        cashRecharge.setBankName(adminBankDto.getBankName());
        cashRecharge.setBankCard(adminBankDto.getBankCard());
        // 调用方法进行时数据的保存
        boolean flag = save(cashRecharge);
        if (!flag) throw new RuntimeException("保存充值记录失败");
        // 返回我们成功的对象
        CashTradeVo cashTradeVo = new CashTradeVo();
        cashTradeVo.setName(adminBankDto.getName());
        cashTradeVo.setBankName(adminBankDto.getBankName());
        cashTradeVo.setBankCard(adminBankDto.getBankCard());
        cashTradeVo.setAmount(mun);
        cashTradeVo.setRemake(remark);
        cashTradeVo.setStatus((byte) 0);
        return cashTradeVo;
    }

    /**
     * 现金的充值审核
     *
     * @param userId                  审核人 id
     * @param cashRechargeAuditRecord 充值审核记录
     * @return true 审核成功 false 审核失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean cashRechargeAudit(Long userId, CashRechargeAuditRecord cashRechargeAuditRecord) {
        // 只能有一个员工进行审核，所以需要分布锁
        // 超时时间 300ms，300ms 没有释放会自动释放，锁的名称为 CASH_RECHARGE_LOCK:cashRechargeAuditRecord.id
        return cache.tryLockAndRun(String.valueOf(cashRechargeAuditRecord.getId()), 300, TimeUnit.MILLISECONDS, () -> {
            // 获取充值记录单
            CashRecharge cashRecharge = getById(cashRechargeAuditRecord.getId());
            // 如果查不到充值记录
            if (cashRecharge == null) throw new IllegalArgumentException("充值记录不存在");
                // 如果充值记录的状态为审核成功
            else if (cashRecharge.getStatus() == 1) throw new IllegalArgumentException("充值记录审核已经通过");
            // 设置操作人
            cashRechargeAuditRecord.setAuditUserId(userId);
            // 设置审核次数
            cashRechargeAuditRecord.setStep((byte) (cashRecharge.getStep() + 1));
            // 对该记录进行插入操作
            boolean flag = cashRechargeAuditRecordService.saveOrUpdate(cashRechargeAuditRecord);
            // 让下面的确定 flag 是 true 才进行后续操作
            Assert.isTrue(flag, "审核记录保存失败");
            // 如果没有审核通过，或者失败都需要做的操作，更换充值记录单状态
            cashRecharge.setStatus(cashRechargeAuditRecord.getStatus());
            cashRecharge.setRemark(cashRechargeAuditRecord.getRemark());
            // 设置审核次数
            cashRecharge.setStep(cashRechargeAuditRecord.getStep());
            // 如果审核失败则进行直接的保存
            if (cashRecharge.getStatus() == 2) {
                // 进行数据的保存`
                flag = updateById(cashRecharge);
                // 让下面的确定 flag 是 true 才进行后续操作
                Assert.isTrue(flag, "充值记录保存失败");
            } else {
                // 进行金钱的转账
                flag = accountService.accountTransfer(userId, cashRecharge.getUserId(), cashRecharge.getCoinId(), cashRecharge.getId(), cashRecharge.getNum(), cashRecharge.getFee(), (byte) 1, "recharge_into", "充值");
                // 让下面的确定 flag 是 true 才进行后续操作
                Assert.isTrue(flag, "转账失败");
                // 设置最后确认到账时间
                cashRecharge.setLastTime(new Date());
                updateById(cashRecharge);
            }
        });
    }

    /**
     * 银行卡进行随机分配获取一张银行卡
     *
     * @param adminBanks 银行卡列表
     * @return 银行卡数据对象
     */
    private AdminBankDto balanced(List<AdminBankDto> adminBanks) {
        if (adminBanks == null || adminBanks.isEmpty())
            throw new IllegalArgumentException("没有找到可用的银行卡数据");
        // 获取银行卡的总数
        int size = adminBanks.size();
        // 如果只有一张银行卡，直接返回
        if (size == 1)
            return adminBanks.get(0);
        // 创建一个随机参数的对象
        Random random = new Random();
        // 获取随机的一张银行卡
        return adminBanks.get(random.nextInt(size));
    }
}
